package com.creatified.jacqueline;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.batik.css.parser.DefaultDocumentHandler;
import org.apache.batik.css.parser.Parser;
import org.apache.batik.css.parser.Scanner;
import org.apache.log4j.Logger;
import org.w3c.css.sac.InputSource;
import org.w3c.css.sac.SelectorList;



/**
 * A helper class to parse the CSS selectors from a CSS file
 */
public final class JacquelineCSSParser extends DefaultDocumentHandler {
	
	private Parser parser;
	
	private String currentUrl;
	
	private int refLineNo = 0;
	
	private int currentLine;
	
	protected static final Logger LOG = Logger.getLogger(JacquelineCSSParser.class);
	
	private ArrayList<JacquelineSelector> selectors;
	
	/**
	 * Instantiates a new JacquelineCSSParser and parses the given file.
	 * 
	 * @param file the CSS file to extract the CSS selectors from
	 */
	public JacquelineCSSParser(File file) throws IOException {
		
		selectors = new ArrayList<JacquelineSelector>();
		
		parser = new Parser();
		parser.setDocumentHandler(this);
		
		LOG.info("Parsing stylesheet '" + file.getPath() + "'.");
		parser.parseStyleSheet(new InputSource(new FileReader(file)));
		LOG.info(file.getPath() + " contains " + selectors.size() + " CSS selectors.");
	}
	
	/**
	 * Get the parsed CSS selectors.
	 * 
	 * @return the CSS selectors
	 */
	protected ArrayList<JacquelineSelector> getSelectors() {
		return selectors;
	}
	
	/* (non-Javadoc)
	 * @see org.apache.batik.css.parser.DefaultDocumentHandler#comment(java.lang.String)
	 */
	@Override
	public void comment(String comment) {
		// TODO - this is too specific, move outside of Jacqueline!
		if(comment.matches("\\*\\*\\*\\s(.*?)\\s")) {
			currentUrl = JacquelineCSSParser.urlFromComment(comment);
			refLineNo = JacquelineCSSParser.getScanner(this.parser).getLine();
			
			LOG.debug("reset File-URL to '" + currentUrl + "'");
			LOG.debug("reset reference Line number at line " + refLineNo);
		}
	}
	
	/* (non-Javadoc)
	 * @see org.apache.batik.css.parser.DefaultDocumentHandler#startSelector(org.w3c.css.sac.SelectorList)
	 */
	@Override
	public void startSelector(SelectorList ruleSet) {
		Scanner s = JacquelineCSSParser.getScanner(this.parser);
		if(s != null) {
			currentLine = s.getLine() - refLineNo;
		}
		for(int i = 0; i < ruleSet.getLength(); i++) {
			if(ruleSet.item(i) != null) {
				try {
					LOG.debug("found selector '" + ruleSet.item(i) + "' at line " + currentLine);
					this.selectors.add(new JacquelineSelector(ruleSet.item(i).toString(), currentUrl, currentLine));
				} catch (JacquelineException e) {
					
					LOG.error(e + " at line " + currentLine);
				}
			}
		}
	}
	
	/**
	 * Utility method to get the Scanner of a Batik parser.
	 * Uses reflection since this property is not publicly accessible.
	 * 
	 * @param p the parser
	 * 
	 * @return the scanner
	 */
	private static Scanner getScanner(Parser p) {
		Scanner s = null;
		Field f = null;
		try {
			f = p.getClass().getDeclaredField("scanner");
			f.setAccessible(true);
			s = (Scanner) f.get(p);
		} catch(Exception e) {
			LOG.error(e);
		}
		return s;
	}
	
	/**
	 * Url from comment.
	 * 
	 * @param comment the comment
	 * 
	 * @return the string
	 */
	private static String urlFromComment(String comment) {
		// TODO too specific for Jacqueline - extract or implement a more generic attempt
		Matcher m = Pattern.compile("\\*\\*\\*\\s(.*?)\\s").matcher(comment);
		m.matches();
		if(m.groupCount() > 0) {
			return m.group(1);
		}
		return comment;
	}
}
